/*
 * 巴法科技:2022-11-22
 * 支持巴法app和小程序配网，长按按键可重新配网
 */
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <EEPROM.h>
#include <Ticker.h>
#include <ArduinoJson.h>
#include <OneButton.h>
#include <ESP8266httpUpdate.h>
WiFiClient client_bemfa_WiFiClient;
HTTPClient http_bemfa_HTTPClient;
// 巴法云服务器地址默认即可
#define TCP_SERVER_ADDR "bemfa.com"
// 服务器端口，tcp创客云端口8344
#define TCP_SERVER_PORT "8344"

//*****可以修改的地方******//
String aptype = "003";  // 001插座类型，002灯类型，003风扇类型，004传感器，005空调，006开关，009窗帘
String verSion = "3";   // 1是mqtt协议，3是tcp协议,5是mqtt V2 版本，7是 tcp V2 版本
String adminID = "";    // 默认空即可。企业id,建议企业用户配置，用户会自动绑定到该企业下，获取id方法见接入文档5.17节
const int pwm = 5;      // 单片机LED引脚值，GPIO0引脚，其他开发板，修改为自己的引脚，例如NodeMCU开发板修改为D4
const int LedBlink = 2; // 指示灯引脚，可自行修改，如果没有指示灯，建议删除指示灯相关代码
bool dfsState = false;  // 风扇状态
bool ytState = false;   // 摇头状态
bool zrfState = false;  // 自然风状态

int failCount = 0; // 定义失败连接次数
int val = 0;       // 定义开关占空比值
#define yaotu 14
// EC11 引脚配置
#define BMQ_A 13
#define BMQ_B 12
OneButton button(0, true);
// 一组数据采集完成的标志
unsigned char flag = 0;
// 当前数据存储位置
unsigned char pos = 0;
// 完整数据采集周期变量
unsigned char chg[8];
// 上次状态
unsigned char status_old = 0;
// 当前状态
unsigned char status_cur = 0;
// 旋转计数，顺时针：+1；逆时针：-1
int a = 80;  // 定义一档
int b = 120; // 定义二档
int c = 160; // 定义三档
int d = 200; // 定义四档
int e = 240; // 定义五档

// 最大字节数
#define MAX_PACKETSIZE 512
// 设置心跳值30s
#define KEEPALIVEATIME 2 * 1000

// tcp客户端相关初始化，默认即可
WiFiClient TCPclient;
String TcpClient_Buff = "";
unsigned int TcpClient_BuffIndex = 0;
unsigned long TcpClient_preTick = 0;
unsigned long preHeartTick = 0;    // 心跳
unsigned long preTCPStartTick = 0; // 连接
bool preTCPConnected = false;

String topicMac = "";

// 相关函数初始化

// EC11 引脚配置

// 连接WIFI
void doWiFiTick();
void startSTA();

// TCP初始化连接
void doTCPClientTick();
void startTCPClient();
void sendtoTCPServer(String p);

void simulateNaturalWind(int initialSpeed);
void dakaifengshan(int targetValue);

int httpCode = 0;
String UID = "4892bd7fe005ecbbccf35929157ec7e7";
String TOPIC = "FengShan";
#define HOST_NAME "bemfa"
char config_flag = 0;
#define MAGIC_NUMBER 0xAA

/**
 * 结构体，用于存储配网信息
 */
struct config_type
{
  char stassid[32];
  char stapsw[64];
  char cuid[40];
  char ctopic[32];
  uint8_t reboot;
  uint8_t magic;
  uint8_t val;
};
config_type config;
void doSmartconfig();
void saveConfig();
void initWiFi();
void loadConfig();
void restoreFactory();
void waitKey();

// 当升级开始时，打印日志
void update_started()
{
  Serial.println("CALLBACK:  HTTP update process started");
}

// 当升级结束时，打印日志
void update_finished()
{
  Serial.println("CALLBACK:  HTTP update process finished");
  digitalWrite(LedBlink, HIGH); // 指示灯引脚
}

// 当升级中，打印日志
void update_progress(int cur, int total)
{
  Serial.printf("CALLBACK:  HTTP update process at %d of %d bytes...\n", cur, total);
  digitalWrite(LedBlink, HIGH); // 指示灯引脚
  delay(50);
  digitalWrite(LedBlink, LOW); // 指示灯引脚
  delay(50);
}

// 当升级失败时，打印日志
void update_error(int err)
{
  Serial.printf("CALLBACK:  HTTP update fatal error code %d\n", err);
}

/**
 * 固件升级函数
 * 在需要升级的地方，加上这个函数即可，例如setup中加的updateBin();
 * 原理：通过http请求获取远程固件，实现升级
 */
void updateBin()
{
  Serial.println("start update");
  // OTA固件链接，请替换为自己的固件链接，如果接收到msg=update，开始执行固件升级
  String upUrl = "http://bin.bemfa.com/b/3BcNDg5MmJkN2ZlMDA1ZWNiYmNjZjM1OTI5MTU3ZWM3ZTc=" + TOPIC + ".bin";
  WiFiClient UpdateClient;
  ESPhttpUpdate.onStart(update_started);     // 当升级开始时
  ESPhttpUpdate.onEnd(update_finished);      // 当升级结束时
  ESPhttpUpdate.onProgress(update_progress); // 当升级中
  ESPhttpUpdate.onError(update_error);       // 当升级失败时

  t_httpUpdate_return ret = ESPhttpUpdate.update(UpdateClient, upUrl);
  switch (ret)
  {
  case HTTP_UPDATE_FAILED: // 当升级失败
    Serial.println("[update] Update failed.");
    break;
  case HTTP_UPDATE_NO_UPDATES: // 当无升级
    Serial.println("[update] Update no Update.");
    break;
  case HTTP_UPDATE_OK: // 当升级成功
    Serial.println("[update] Update ok.");
    break;
  }
}

Ticker delayTimer;
void delayRestart(float t)
{
  delayTimer.attach(t, []()
                    { ESP.restart(); });
}

void getUid(String mac, bool reConfig)
{
  Serial.println("读取私钥.........");
  if (strcmp(config.cuid, "4892bd7fe005ecbbccf35929157ec7e7") != 0 || reConfig)
  {
    http_bemfa_HTTPClient.begin(client_bemfa_WiFiClient, "http://api.bemfa.com/api/device/v1/airkiss/?topic=" + mac + "&version=" + verSion + "&ad=" + adminID);
    httpCode = http_bemfa_HTTPClient.GET();
    if (httpCode > 0)
    {
      String payload = http_bemfa_HTTPClient.getString();
      // json数据解析
      StaticJsonDocument<200> doc;
      DeserializationError error = deserializeJson(doc, payload);
      if (error)
      {
        Serial.print(F("deserializeJson() failed: "));
        Serial.println(error.c_str());
      }
      String code = doc["code"];
      if (code == "5723200")
      {

        String docUID = doc["uid"];
        strcpy(config.cuid, docUID.c_str());
        strcpy(config.ctopic, mac.c_str());
        saveConfig();
      }
      else
      {
        Serial.println(" config ERROR.........");
      }

      http_bemfa_HTTPClient.end();
    }
  }
  if (strcmp(config.ctopic, "") == 0)
  {
    TOPIC = mac;
  }
  else
  {
    TOPIC = config.ctopic;
  }
  UID = config.cuid;
}

/**
 * airkiss配网
 */
void doSmartconfig()
{
  Serial.println("正在等待配网...");
  WiFi.disconnect();
  WiFi.mode(WIFI_STA);
  WiFi.stopSmartConfig();
  WiFi.beginSmartConfig();
  while (!WiFi.smartConfigDone())
  {
    digitalWrite(LedBlink, HIGH); // 指示灯引脚
    delay(100);
    digitalWrite(LedBlink, LOW); // 指示灯引脚
    delay(100);
    if (WiFi.smartConfigDone())
    {
      strcpy(config.stassid, WiFi.SSID().c_str());
      strcpy(config.stapsw, WiFi.psk().c_str());
      config.magic = 0xAA;
      saveConfig();
      Serial.println(config.stassid);
      Serial.println(config.stapsw);
    }
  }
  Serial.println("配网成功");
  WiFi.disconnect();                         // 断开连接
  WiFi.mode(WIFI_STA);                       // STA模式
  WiFi.begin(config.stassid, config.stapsw); // 连接路由器
  Serial.println("正在连接网络...");
  while (WiFi.status() != WL_CONNECTED)
  {
    digitalWrite(LedBlink, LOW); // 指示灯引脚
    delay(500);
    digitalWrite(LedBlink, HIGH); // 指示灯引脚
    delay(500);
  }
  delay(1000);
}

/**
 * 存储配网信息
 */
void saveConfig()
{
  uint8_t mac[6];
  WiFi.macAddress(mac);
  config.val = val;
  config.reboot = 0;
  EEPROM.begin(256);
  uint8_t *p = (uint8_t *)(&config);
  for (int i = 0; i < sizeof(config); i++)
  {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();
}

/**
 * 初始化wifi信息，并连接路由器网络
 */
void initWiFi()
{

  if (WiFi.status() != WL_CONNECTED)
  {
    WiFi.disconnect();                         // 断开连接
    WiFi.mode(WIFI_STA);                       // STA模式
    WiFi.begin(config.stassid, config.stapsw); // 连接路由器
    Serial.println("正在连接网络...");
  }
}
/**
 * 加载存储的信息，并检查是否进行了反复5次重启恢复出厂信息
 */
uint8_t *p = (uint8_t *)(&config);
void loadConfig()
{
  uint8_t mac[6];
  WiFi.macAddress(mac);
  EEPROM.begin(256);
  for (int i = 0; i < sizeof(config); i++)
  {
    *(p + i) = EEPROM.read(i);
  }
  Serial.println(config.cuid);
  Serial.println(config.ctopic);
  Serial.println(config.stassid);
  Serial.println(config.stapsw);
  Serial.println(config.magic);
  Serial.println(config.val);
  config.reboot = config.reboot + 1;
  if (config.reboot >= 10)
  {
    restoreFactory();
  }
  if (config.magic != 0xAA)
  {
    config_flag = 1;
  }
  EEPROM.begin(256);
  for (int i = 0; i < sizeof(config); i++) // 写数据
  {
    EEPROM.write(i, *(p + i)); // 写数据
  }
  EEPROM.commit(); // 保存更改的数据
  delay(500);
  EEPROM.begin(256);
  config.reboot = 0;
  for (int i = 0; i < sizeof(config); i++)
  {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();
}
/**
 * 恢复出厂设置，清除存储的wifi信息
 */
void restoreFactory()
{
  Serial.println("Restore Factory....... ");
  config.magic = 0x00;
  strcpy(config.stassid, "");
  strcpy(config.stapsw, "");
  strcpy(config.cuid, "");
  strcpy(config.ctopic, "");
  saveConfig();
  delayRestart(1);
  while (1)
  {
    delay(100);
  }
}
/**
 * 检查是否需要airkiss配网
 */
void waitKey()
{
  if (config_flag == 1)
  {
    doSmartconfig();
  }
}
/*
 *发送数据到TCP服务器
 */
void sendtoTCPServer(String p)
{
  if (!TCPclient.print(p))
  {
    Serial.println("客户端发送失败");
    startTCPClient(); // 客户端重新连接
  }
  preHeartTick = millis(); // 心跳计时开始，需要每隔60秒发送一次数据
}

/*
 *初始化和服务器建立连接
 */
void startTCPClient()
{
  Serial.println("连接客户端...");
  Serial.println("uid=" + UID + "&topic=" + TOPIC + "");

  if (TCPclient.connect(TCP_SERVER_ADDR, atoi(TCP_SERVER_PORT)))
  {
    Serial.println("客户端连接成功");
    String tcpTemp = "";                                       // 初始化字符串
    tcpTemp = "cmd=1&uid=" + UID + "&topic=" + TOPIC + "\r\n"; // 构建订阅指令
    sendtoTCPServer(tcpTemp);                                  // 发送订阅指令
    tcpTemp = "";                                              // 清空
    preTCPConnected = true;
    TCPclient.setNoDelay(true);
    failCount = 0;
  }
  else
  {
    failCount = failCount + 1;
    if (failCount > 3) // 如果失败连接3次
    {
      WiFi.disconnect(); // 断开连接
    }
    Serial.println("客户端连接失败");
    TCPclient.stop();
    preTCPConnected = false;
  }
  preTCPStartTick = millis();
}
String convert(int State)
{
  if (State)
  {
    return "on";
  }
  else
  {
    return "off";
  }
}

/*
 *检查数据，发送心跳
 */
void doTCPClientTick()
{
  // 检查是否断开，断开后重连
  if (WiFi.status() != WL_CONNECTED)
  {
    return;
  }
  if (!TCPclient.connected())
  {                                            // 断开重连
    if (millis() - preTCPStartTick > 1 * 3000) // 重新连接
    {
      startTCPClient();
    }
  }
  else
  {
    if (TCPclient.available())
    { // 收数据
      char c = TCPclient.read();
      TcpClient_Buff += c;
      TcpClient_BuffIndex++;
      TcpClient_preTick = millis();

      if (TcpClient_BuffIndex >= MAX_PACKETSIZE - 1)
      {
        TcpClient_BuffIndex = MAX_PACKETSIZE - 2;
        TcpClient_preTick = TcpClient_preTick - 200;
      }
    }
    if (millis() - preHeartTick >= KEEPALIVEATIME)
    { // 保持心跳
      sendtoTCPServer("cmd=0&msg=keep\r\n");
      sendtoTCPServer("cmd=2&uid=" + UID + "&topic=" + TOPIC + "/up&msg=" + convert(dfsState) + "#" + convert(ytState) + "#" + val + "\r\n"); // 推送消息
    }
    if ((TcpClient_Buff.length() >= 20) && (millis() - TcpClient_preTick >= 200))
    {
      String fan_on = "on#" + String(val) + "#1";
      String fan_off = "on#" + String(val) + "#0";

      TCPclient.flush();
      if (TcpClient_Buff.indexOf(fan_on) > 0)
      {                            // 如果是消息==打开
        digitalWrite(yaotu, HIGH); // 指示灯引脚
        ytState = true;            // 改变记录的状态
      }
      else if (TcpClient_Buff.indexOf(fan_off) > 0)
      {                           // 如果是消息==打开
        digitalWrite(yaotu, LOW); // 指示灯引脚
        ytState = false;          // 改变记录的状态
      }
      else if (TcpClient_Buff.indexOf("on#0") > 0)
      {                   // 如果是消息==打开
        dakaifengshan(a); // 打开灯
        dfsState = true;  // 改变记录的状态
      }
      else if (TcpClient_Buff.indexOf("on#1") > 0)
      {                   // 如果是消息==打开
        dakaifengshan(b); // 打开灯
        dfsState = true;  // 改变记录的状态
      }
      else if (TcpClient_Buff.indexOf("on#2") > 0)
      {                   // 如果是消息==打开
        dakaifengshan(c); // 打开灯
        dfsState = true;  // 改变记录的状态
      }
      else if (TcpClient_Buff.indexOf("on#3") > 0)
      {                   // 如果是消息==打开
        dakaifengshan(d); // 打开灯
        dfsState = true;  // 改变记录的状态
      }
      else if (TcpClient_Buff.indexOf("on#4") > 0)
      {                   // 如果是消息==打开
        dakaifengshan(e); // 打开灯
        dfsState = true;  // 改变记录的状态
      }
      else if (TcpClient_Buff.indexOf("off") > 0)
      { // 如果是消息==关闭
        dakaifengshan(0);
        zrfState = false;
        dfsState = false; // 改变记录的状态
      }
      else if (TcpClient_Buff.indexOf("on") > 0)
      { // 如果是消息==打开
        if (val == 240)
        {
          zrfState = true;
        }
        else
        {
          dakaifengshan(e); // D1
        }
        dfsState = true; // 改变记录的状态
      }
      else if (TcpClient_Buff.indexOf("update") > 0)
      {
        Serial.println("[update] Update Start......");
        updateBin();
      }
      TcpClient_Buff = "";
      TcpClient_BuffIndex = 0;
    }
  }
}
int minSpeed = 30; // 最小风速
static uint32_t lastWiFiCheckTick = 0;
void simulateNaturalWind(int maxSpeed)
{
  if (zrfState && millis() - lastWiFiCheckTick > random(2000, 4000))
  {
    int currentSpeed = random(minSpeed, maxSpeed); // 随机生成一个风速

    analogWrite(pwm, currentSpeed); // 假设使用 9 号引脚输出 PWM
    lastWiFiCheckTick = millis();
  }
}
void dakaifengshan(int targetValue)
{
  int step = 1; // 每次增加或减少的值，可根据需要调整
  while (val != targetValue)
  {
    if (val < targetValue)
    {
      if (val == 0)
      {
        val = 50;
      }
      val += step;
      if (val > targetValue)
      {
        val = targetValue;
      }
    }
    else
    {
      if (val == 50)
      {
        val = 0;
      }
      val -= step;
      if (val < targetValue)
      {
        val = targetValue;
      }
    }
    dfsState = val; // 改变记录的状态
    analogWrite(pwm, val); // 假设使用 9 号引脚输出 PWM

    delay(20); // 延迟 50 毫秒，体现缓慢变化
  }
  saveConfig();
}
void startSTA()
{
  WiFi.disconnect();                         // 断开连接
  WiFi.mode(WIFI_STA);                       // STA模式
  WiFi.begin(config.stassid, config.stapsw); // 连接路由器
}

/**************************************************************************
                                 WIFI
***************************************************************************/
/*
  WiFiTick
  检查是否需要初始化WiFi
  检查WiFi是否连接上，若连接成功启动TCP Client
  控制指示灯
*/
void doWiFiTick()
{
  static bool taskStarted = false;
  static uint32_t lastWiFiCheckTick = 0;

  if (WiFi.status() != WL_CONNECTED) // 检查是否连接成功
  {
    if (millis() - lastWiFiCheckTick > 60000) // 未连接60s重连
    {
      Serial.println("网络未连接重连");
      startSTA();
      taskStarted = false;
      lastWiFiCheckTick = millis();
    }
    digitalWrite(LedBlink, LOW); // 指示灯引脚
    delay(500);
    digitalWrite(LedBlink, HIGH); // 指示灯引脚
    delay(500);
  }
  else if (taskStarted == false)
  {
    Serial.println("网络连接成功");
    Serial.print("IP地址:");
    Serial.println(WiFi.localIP());
    taskStarted = true;
    startTCPClient();
  }
}

// 按键单击回调函数
void click()
{
  if (dfsState)
  {
    dfsState = !dfsState;
    dakaifengshan(0);
  }
  else
  {
    dfsState = !dfsState;
    dakaifengshan(e);
  }
}

// 按键双击回调函数
void doubleclick()
{
  if (ytState)
  {
    ytState = !ytState;
    digitalWrite(yaotu, LOW); // 指示灯引脚
  }
  else
  {
    ytState = !ytState;
    digitalWrite(yaotu, HIGH); // 指示灯引脚
  }
}
void duringLongPress()
{
  zrfState = !zrfState;
}
void com_proc()
{

  int v = 0;

  if (flag == 0)
  {

    // 当前状态

    status_cur = digitalRead(BMQ_A) * 2 + digitalRead(BMQ_B);

    if (status_cur != status_old)
    {

      Serial.print("pos=");

      Serial.print(pos);

      Serial.print("  AB=");

      Serial.println(status_cur);

      // 有变化

      status_old = status_cur;

      // 保存

      chg[pos++] = status_cur;

      if (pos >= 4)
      {

        // 完成一组数据采集

        flag = 1; // 设置标志，防止下一次中断打断下面的处理

        // 计算这组数据的变化顺序

        v = chg[0] * 1000 + chg[1] * 100 + chg[2] * 10 + chg[3];

        // 为了能避免用户在旋转不到位时进行反方向的旋转，需要判断多种值的判定

        // 正常的顺时针旋转为2310，逆时针为1320，通过切换组合顺序，可以避免漏判

        if (v == 2310 || v == 3102 || v == 1023 || v == 231)
        {
          // 顺时针转动
          if (val < 240)
          {
            if (val == 0)
            {
              val = val + 50;
            }
            else
            {
              val = val + 5;
            }

            Serial.print("Shun:");
            Serial.println(val);
            analogWrite(pwm, val);
          }
        }
        else if (v == 1320 || v == 3201 || v == 2013 || v == 132)
        {

          // 逆时针转动
          if (val > 0)
          {
            if (val == 50)
            {
              val = val - 50;
            }
            else
            {
              val = val - 5;
            }
            Serial.print("Ni:");
            Serial.println(val);
            analogWrite(pwm, val);
          }
        }

        if (v == 1020 || v == 1013 || v == 2023 || v == 2020)
        {

          // 转了一半不动，或者回去了，保留后两位数据

          chg[0] = chg[2];

          chg[1] = chg[3];

          pos = 2;

          // 恢复标志

          flag = 0;
        }
        else
        {

          // 存储位置归位

          pos = 0;

          // 恢复标志

          flag = 0;
        }
      }
    }
  }
  saveConfig();
}
// 中断处理函数，边沿触发

IRAM_ATTR void a_proc()
{
  com_proc();
}

IRAM_ATTR void b_proc()
{
  com_proc();
}

// 初始化，相当于main 函数
void setup()
{
  Serial.begin(115200);
  pinMode(LedBlink, OUTPUT);
  pinMode(yaotu, OUTPUT);
  digitalWrite(LedBlink, LOW); // 指示灯引脚
  pinMode(4, OUTPUT);
  digitalWrite(4, HIGH);
  // //初始化按键事件检测
  // pinMode(0, INPUT_PULLUP);
  // attachInterrupt(digitalPinToInterrupt(5), checkSwitch, RISING); // 设置中断
  button.reset();                        // 清除按钮状态机的状态
  button.attachClick(click);             // 注册单击
  button.attachDoubleClick(doubleclick); // 注册双击
  // button.attachLongPressStart(longPressStart);//注册长按开始
  button.attachDuringLongPress(duringLongPress); // 注册长按
  // button.attachLongPressStop(longPressStop);//注册长按结束
  // button.attachDuringLongPress(attachPressStart);//按下键就会持续触发
  pinMode(BMQ_A, INPUT); // D1 - A
  pinMode(BMQ_B, INPUT); // D2 - B
  // 设置中断，下降沿和上升沿均触发中断
  attachInterrupt(digitalPinToInterrupt(BMQ_A), a_proc, FALLING | RISING);
  attachInterrupt(digitalPinToInterrupt(BMQ_B), b_proc, FALLING | RISING);
  status_old = digitalRead(BMQ_A) * 2 + digitalRead(BMQ_B);
  Serial.println("");
  Serial.println("初始化...");
  loadConfig();
  waitKey();
  initWiFi();
  TOPIC = WiFi.macAddress().substring(8); // 取mac地址做主题用
  TOPIC.replace(":", "");                 // 去掉:号
  TOPIC = TOPIC + aptype;
  getUid(TOPIC, false);
  digitalWrite(LedBlink, LOW); // 指示灯引脚
  dakaifengshan(config.val);   // D1
}

void loop()
{
  doWiFiTick();      // 检查wifi
  doTCPClientTick(); // tcp消息接收
  button.tick();
  simulateNaturalWind(val);
}